#include <errno.h>
#include "Global.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <cstdio>
#include <arpa/inet.h>
#include <pthread.h>
#include <strings.h>
#include "Request.h"
#include "Response.h"
#include <cstdlib>
#include <queue>
#include <sstream>
#include <fcntl.h>
#include <signal.h>
#include "Log.h"
using namespace std;

string basedir;
struct locklog
{
    pthread_mutex_t lock;
    int fd;
};
struct client
{
    string ip;
    int socketfd;
};
struct locklog * log;

void * listenForRequests(int descriptor);
queue<Request *> GenerateRequestObjects(string data);
void HandleRequestObjects(queue<Request *> reqs, int descriptor, string ip);
static void CleanupOnSignal(int signum);

static void CleanupOnSignal(int signum)
{
    if(signum == SIGINT || signum == SIGQUIT)
    {
        pthread_mutex_lock(&log->lock);
        close(log->fd);
        pthread_mutex_unlock(&log->lock);
        exit(0);
    }
}
void * listenForRequests(void * descriptor) {
    
    const int BUFLEN = 1024;
    ssize_t bytesRead;
    char buffer[BUFLEN];
    struct client desc = *((struct client *)descriptor);
    //check to see if the connection is closed
    while(recv(desc.socketfd,buffer,1024,MSG_PEEK) > 0)
    {
        stringstream data(stringstream :: out);
        bytesRead = BUFLEN;
        while (bytesRead == BUFLEN) {
            bytesRead = recv(desc.socketfd, buffer, BUFLEN,0);
            //bytesWritten = write(descriptor, buffer, bytesRead);
            data.write(buffer,bytesRead);
        //    cout.write(buffer,bytesRead);
        }
        queue<Request *> reqs = GenerateRequestObjects(data.str());
        pthread_mutex_lock(&log->lock);
        cout << "LOL" << desc.ip;
        string msg = MakeRequestLogMessage(desc.ip,reqs);
        write(log->fd,msg.c_str(),msg.size());
        pthread_mutex_unlock(&log->lock);
        HandleRequestObjects(reqs,desc.socketfd,desc.ip);
    }
    close(desc.socketfd);
    pthread_mutex_lock(&log->lock);
    string msg = MakeDisconnectMessage(desc.ip);
    write(log->fd,msg.c_str(),msg.size());
    pthread_mutex_unlock(&log->lock);
    //delete[] descriptor;
    pthread_exit((void *)0);
}

queue<Request *> GenerateRequestObjects(string data) {
    queue<Request *> openRequests;
    stringstream headers(data,stringstream::in);
    while (!headers.eof() && headers.peek() > 0) {
        Request * req = Request::Create(headers);
        //(*req).PrintInfo();
        openRequests.push(req);
    }
    return openRequests;
}

void HandleRequestObjects(queue<Request *> reqs, int descriptor, string ip)
{
    while(!reqs.empty())
    {
        Response resp = (*reqs.front()).processRequest();
        Response::SendResponse(resp,descriptor,basedir);
        pthread_mutex_lock(&log->lock);
        string msg = MakeResponseLogMessage(ip,resp);
        write(log->fd,msg.c_str(),msg.size());
        pthread_mutex_unlock(&log->lock);
        reqs.pop();
    }
}

int main(int argCount, char * argValues[]) {
    log = (struct locklog *)malloc(sizeof(struct locklog));
    if(pthread_mutex_init(&log->lock, NULL) != 0)
    {
        free(log);
        perror("Could not instantiate log lock.");
        exit(-1);
    }
    log->fd = open("log",O_WRONLY | O_CREAT | O_APPEND,S_IRWXU);
    int listenDescriptor;
    //int connectionDescriptor;
    uint16_t myPort;
    int status;
    string data;
    struct sockaddr_in serverAddress, clientAddress;
    socklen_t clientLen = sizeof(sockaddr_in);
    const int queueSize = 10;
    int on = 1;

    if (argCount != 4) {
        fprintf(stderr, "Usage: %s <address> <port number> <base dir>\n", argValues[0]);
        exit(-1);
    }
    basedir = argValues[3];
    myPort = atoi(argValues[2]);
    listenDescriptor = socket(AF_INET, SOCK_STREAM, 0);
    setsockopt(listenDescriptor, SOL_SOCKET, SO_REUSEADDR, (char *) & on, sizeof (on));
    bzero((char *) &serverAddress, sizeof (serverAddress));
    serverAddress.sin_family = AF_INET;
    //serverAddress.sin_addr.s_addr = INADDR_ANY;
    int canResolveAddr = inet_aton(argValues[1],&serverAddress.sin_addr);
    if(canResolveAddr == 0)
    {
        perror("Cannot resolve address.");
        exit(-1);
    }
    serverAddress.sin_port = htons(myPort);
    status = bind(listenDescriptor, (struct sockaddr *) &serverAddress, sizeof (serverAddress));
    if (status < 0) {
        perror("Bind error");
        exit(-1);
    }
    do {
        status = listen(listenDescriptor, queueSize);
        if (status < 0) {
            perror("Listener Error");
            exit(-1);
        }
        struct client * connection = new struct client;
        (*connection).socketfd = accept(listenDescriptor, (struct sockaddr *) &clientAddress, &clientLen);
        if ((*connection).socketfd < 0) {
            perror("Error accepting connection ");
            printf("%d", errno);
            exit(-1);
        } else {
            char str[INET_ADDRSTRLEN];
            inet_ntop(AF_INET, &clientAddress.sin_addr, str, sizeof (str));
            (*connection).ip = string(str);
            printf("Client address is %s\n", (*connection).ip.c_str());
            pthread_mutex_lock(&log->lock);
            string msg = MakeConnectMessage(str);
            write(log->fd,msg.c_str(),msg.size());
            pthread_mutex_unlock(&log->lock);
            pthread_t tid;
            pthread_create(&tid,NULL,listenForRequests,(void *)(connection));            
        }
        if(signal(SIGINT, CleanupOnSignal) == SIG_ERR || signal(SIGQUIT, CleanupOnSignal) == SIG_ERR)
        {
            perror("Unable to catch interrupt of quit signal.");
        }
    } while (true);
    //close(connectionDescriptor);
    return EXIT_SUCCESS;
}